package ar.edu.uba.fi.jmit.aspecter;

import java.lang.reflect.InvocationTargetException;

import javassist.ClassPool;
import javassist.CtClass;
import ar.edu.uba.fi.jmit.aspecter.core.interceptor.FieldAccessInterceptor;
import ar.edu.uba.fi.jmit.aspecter.core.interceptor.FieldAccessType;
import ar.edu.uba.fi.jmit.aspecter.testUtils.ExceptionBeforeFieldAccessInterceptor;

public class FieldAccessInterceptorTestCase extends AspecterTestCase {

	public void testIntercept() {
		try {
			String className = getNextClassName();
			FieldAccessInterceptor interceptor = new ExceptionBeforeFieldAccessInterceptor(className, "x");
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest10", className);
			ctClass.instrument(interceptor);
			Class<?> clazz = ctClass.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("setY",new Class<?>[]{int.class}).invoke(point, 10);
			clazz.getMethod("getY").invoke(point);
			try {
				clazz.getMethod("setX",new Class<?>[]{int.class}).invoke(point, 10);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			
			try {
				clazz.getMethod("getX").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInterceptReads() {
		try {
			String className = getNextClassName();
			FieldAccessInterceptor interceptor = new ExceptionBeforeFieldAccessInterceptor(className, "x", FieldAccessType.READ);
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest10", className);
			ctClass.instrument(interceptor);
			Class<?> clazz = ctClass.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("setY",new Class<?>[]{int.class}).invoke(point, 10);
			clazz.getMethod("getY").invoke(point);
			clazz.getMethod("setX",new Class<?>[]{int.class}).invoke(point, 10);
			try {
				clazz.getMethod("getX").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInterceptWrites() {
		try {
			String className = getNextClassName();
			FieldAccessInterceptor interceptor = new ExceptionBeforeFieldAccessInterceptor(className, "x", FieldAccessType.WRITE);
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest10", className);
			ctClass.instrument(interceptor);
			Class<?> clazz = ctClass.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("setY",new Class<?>[]{int.class}).invoke(point, 10);
			clazz.getMethod("getY").invoke(point);
			clazz.getMethod("getX").invoke(point);
			try {
				clazz.getMethod("setX",new Class<?>[]{int.class}).invoke(point, 10);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInterceptObjectField() {
		try {
			String className = getNextClassName();
			FieldAccessInterceptor interceptor = new ExceptionBeforeFieldAccessInterceptor(className, "z", FieldAccessType.BOTH);
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest10", className);
			ctClass.instrument(interceptor);
			Class<?> clazz = ctClass.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("setY",new Class<?>[]{int.class}).invoke(point, 10);
			clazz.getMethod("getY").invoke(point);
			clazz.getMethod("getX").invoke(point);
			clazz.getMethod("setX",new Class<?>[]{int.class}).invoke(point, 10);
			try {
				clazz.getMethod("setZ",new Class<?>[]{Integer.class}).invoke(point, new Integer(10));
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			try {
				clazz.getMethod("getZ").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInterceptStringField() {
		try {
			String className = getNextClassName();
			FieldAccessInterceptor interceptor = new ExceptionBeforeFieldAccessInterceptor(className, "name", FieldAccessType.BOTH);
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest10", className);
			ctClass.instrument(interceptor);
			Class<?> clazz = ctClass.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("setY",new Class<?>[]{int.class}).invoke(point, 10);
			clazz.getMethod("getY").invoke(point);
			clazz.getMethod("getX").invoke(point);
			clazz.getMethod("setX",new Class<?>[]{int.class}).invoke(point, 10);
			try {
				clazz.getMethod("setName",new Class<?>[]{String.class}).invoke(point, "the new name");
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			try {
				clazz.getMethod("getName").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInterceptMutableField() {
		try {
			String className = getNextClassName();
			FieldAccessInterceptor interceptor = new ExceptionBeforeFieldAccessInterceptor(className, "mutableObject", FieldAccessType.BOTH);
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest10", className);
			ctClass.instrument(interceptor);
			Class<?> clazz = ctClass.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("setY",new Class<?>[]{int.class}).invoke(point, 10);
			clazz.getMethod("getY").invoke(point);
			clazz.getMethod("getX").invoke(point);
			clazz.getMethod("setX",new Class<?>[]{int.class}).invoke(point, 10);
			try {
				clazz.getMethod("changePoint").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			try {
				clazz.getMethod("getCoordenateFromPoint").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
		} catch (Exception e) {
			fail(e);
		}
	}
	
	public void testInterceptMutableFieldOnWrite() {
		try {
			String className = getNextClassName();
			FieldAccessInterceptor interceptor = new ExceptionBeforeFieldAccessInterceptor(className, "mutableObject", FieldAccessType.WRITE);
			CtClass ctClass = ClassPool.getDefault().getAndRename("ar.edu.uba.fi.jmit.aspecter.testUtils.PointTest10", className);
			ctClass.instrument(interceptor);
			Class<?> clazz = ctClass.toClass();
			Object point = clazz.newInstance();
			clazz.getMethod("setY",new Class<?>[]{int.class}).invoke(point, 10);
			clazz.getMethod("getY").invoke(point);
			clazz.getMethod("getX").invoke(point);
			clazz.getMethod("setX",new Class<?>[]{int.class}).invoke(point, 10);
			try {
				clazz.getMethod("changePoint").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				//here should be wrapped the runtime exception
				assertEquals(RuntimeException.class, e.getTargetException().getClass());
				assertEquals("intercepted!", e.getTargetException().getMessage());
			}
			try {
				clazz.getMethod("getCoordenateFromPoint").invoke(point);
				fail("Exception should be thrown!");
			}catch(InvocationTargetException e) {
				assertEquals(NullPointerException.class, e.getTargetException().getClass());
				//expected. There was no interception
			}
		} catch (Exception e) {
			fail(e);
		}
	}
}
